/*
 * MagNum.cpp
 *
 *  Created on: Apr 18, 2011
 *      Author: rego
 */

#include "MagNum.h"
#include "Over100Listener.h"
#include "CriticalPrecisionListener.h"
#include <string>

const int 		MagNum::INPUT_BASE = 10;
int				MagNum::INITIAL_PRECISION = 200;
int				MagNum::criticalPrecision = -30;
bool			MagNum::initialized = MagNum::staticInitialization();
mpfr_rnd_t		MagNum::RND_MODE = mpfr_get_default_rounding_mode();
std::list<CriticalPrecisionListener*> MagNum::globalCriticalPrecisionListeners = std::list<CriticalPrecisionListener*>();


const int MagNum::ADD_CORRECTION = 1;
const int MagNum::SUBTRACT_CORRECTION = 0;
const int MagNum::MULTIPLY_CORRECTION = 2;
const int MagNum::DIVIDE_CORRECTION = 2;
const int MagNum::ROOT_CORRECTION = 1;
const int MagNum::EXP_CORRECTION = 0;
const int MagNum::POW_CORRECTION = 0;
const int MagNum::SIN_CORRECTION = 0;

bool MagNum::staticInitialization(){
	mpfr_set_default_prec(MagNum::INITIAL_PRECISION);
	return true;
}

MagNum::MagNum() {
	preinit();
	mpfr_init2(number, INITIAL_PRECISION);
	mpfr_set_si(number, 0, RND_MODE);
	init();
}

MagNum::MagNum(const int given) {
	preinit();
	mpfr_init2(number, INITIAL_PRECISION);
	mpfr_set_si(number, given, RND_MODE);
	init();
}

MagNum::MagNum(const long given) {
	preinit();
	mpfr_init2(number, INITIAL_PRECISION);
	mpfr_set_si(number, given, RND_MODE);
	init();
}

MagNum::MagNum(const double given) {
	preinit();
	mpfr_init2(number, INITIAL_PRECISION);
	mpfr_set_d(number, given, RND_MODE);
	init();
}

MagNum::MagNum(const char* given) {
	preinit();
	mpfr_init2(number, INITIAL_PRECISION);
	mpfr_set_str(number, given, INPUT_BASE, RND_MODE);
	init();
}

MagNum::MagNum(const MagNum& given) {
	preinit();
	mpfr_init2(number, INITIAL_PRECISION);
	mpfr_set(number, *(given.getMpfr()), RND_MODE);
	over100Listeners = given.over100Listeners;
	init();
	currentPrecision = given.getCurrentPrecision();
	if(currentPrecision > criticalPrecision)
		notifyCriticalPositionListeners();
}

MagNum::MagNum(mpfr_t* mpfr) {
	preinit();
	mpfr_init2(number, INITIAL_PRECISION);
	mpfr_set(number, *mpfr, RND_MODE);
	init();
}

void MagNum::preinit() {
	outStringPointer = new char*;
	*outStringPointer = NULL;
}

void MagNum::init() {
	currentPrecision = -mpfr_get_default_prec()+1;

	if(*outStringPointer != NULL) {
		delete [] (*outStringPointer);
	}
}

MagNum::~MagNum() {
	mpfr_clear(number);

	if(*outStringPointer != NULL)
		delete [] (*outStringPointer);
	if(outStringPointer != NULL)
		delete outStringPointer;
}

int MagNum::getInt() const {
	return mpfr_get_si(number, RND_MODE);
}

double MagNum::getDouble() const {
	return mpfr_get_d(number, RND_MODE);
}

char* MagNum::getChars() const {
	if(*outStringPointer == NULL)
		*outStringPointer = new char[MagNum::INITIAL_PRECISION*2+5];
	mpfr_sprintf(*outStringPointer, "%.200Rf", *getMpfr());
	return *outStringPointer;
}

const mpfr_t* MagNum::getMpfr() const {
	return &number;
}

int MagNum::getMpfrExp() const {
	if(mpfr_zero_p(*getMpfr())) {
		return 0;
	}
	return ((__mpfr_struct)(*(getMpfr()))[0])._mpfr_exp;
}

mp_limb_t* MagNum::getMpfrD() const {
	return ((__mpfr_struct)(*(getMpfr()))[0])._mpfr_d;
}

MagNum& MagNum::operator=(const MagNum &other) {
	if(this != &other) {
		mpfr_clear(number);
		mpfr_init2(number, INITIAL_PRECISION);
		mpfr_set(number, *(other.getMpfr()), RND_MODE);
		init();

		currentPrecision = other.getCurrentPrecision();
		if(currentPrecision > criticalPrecision)
			notifyCriticalPositionListeners();
	}
	return *this;

}

MagNum MagNum::operator-() {
	MagNum result = *this;
	result.changeSign();
	return result;
}

MagNum& MagNum::operator+=(const MagNum &other) {

	int oldExp = getMpfrExp();
	int oldPrecision = getCurrentPrecision();

	mpfr_add(number, number, *(other.getMpfr()), RND_MODE);

	evaluateNewPrecision(MAX(other.getMpfrExp(), oldExp), MAX(other.getCurrentPrecision(), oldPrecision), ADD_CORRECTION);
	notifyOver100Listeners();
	return *this;
}

MagNum& MagNum::operator-=(const MagNum &other) {

	int oldExp = getMpfrExp();
	int oldPrecision = getCurrentPrecision();

	mpfr_sub(number, number, *(other.getMpfr()), RND_MODE);

	evaluateNewPrecision(MAX(other.getMpfrExp(), oldExp), MAX(other.getCurrentPrecision(), oldPrecision), SUBTRACT_CORRECTION);
	notifyOver100Listeners();
	return *this;
}

MagNum& MagNum::operator*=(const MagNum &other) {

	int oldExp = getMpfrExp();
	int oldPrecision = getCurrentPrecision();

	mpfr_mul(number, number, *(other.getMpfr()), RND_MODE);

	evaluateNewPrecision(MIN(other.getMpfrExp(), oldExp), MAX(other.getCurrentPrecision(), oldPrecision), MULTIPLY_CORRECTION);
	notifyOver100Listeners();
	return *this;
}

MagNum& MagNum::operator/=(const MagNum &other) {

	int oldExp = getMpfrExp();
	int oldPrecision = getCurrentPrecision();

	mpfr_div(number, number, *(other.getMpfr()), RND_MODE);

	evaluateNewPrecision(MIN(other.getMpfrExp(), oldExp), MAX(other.getCurrentPrecision(), oldPrecision), DIVIDE_CORRECTION);
	notifyOver100Listeners();
	return *this;
}


const MagNum MagNum::operator+(const MagNum &other) const {
	MagNum result = *this;
	result += other;
	return result;
}

const MagNum MagNum::operator-(const MagNum &other) const {
	MagNum result = *this;
	result -= other;
	return result;
}

const MagNum MagNum::operator*(const MagNum &other) const {
	MagNum result = *this;
	result *= other;
	return result;
}

const MagNum MagNum::operator/(const MagNum &other) const {
	MagNum result = *this;
	result /= other;
	return result;
}


bool MagNum::operator==(const MagNum &other) const {
	return mpfr_equal_p(*(*this).getMpfr(), *(other.getMpfr())) != 0;
}

bool MagNum::operator!=(const MagNum &other) const {
	return !((*this)==other);
}

bool MagNum::operator>=(const MagNum &other) const {
	return mpfr_greaterequal_p(*(*this).getMpfr(), *other.getMpfr()) != 0;
}

bool MagNum::operator>(const MagNum &other) const {
	return mpfr_greater_p(*(*this).getMpfr(), *other.getMpfr()) != 0;
}

bool MagNum::operator<=(const MagNum &other) const {
	return mpfr_lessequal_p(*(*this).getMpfr(), *other.getMpfr()) != 0;
}

bool MagNum::operator<(const MagNum &other) const {
	return mpfr_less_p(*(*this).getMpfr(), *other.getMpfr()) != 0;
}

std::ostream& operator<< (std::ostream &out, const MagNum &magnum) {
	char* outString = magnum.getChars();
	out << std::string(outString);
	return out;
}

MagNum& MagNum::changeSign() {
	if(mpfr_zero_p(number) == 0)
		mpfr_neg(number, number, RND_MODE);
	return *this;
}

MagNum& MagNum::abs() {
	if(mpfr_zero_p(number) == 0)
		mpfr_abs(number, number, RND_MODE);
	return *this;
}

MagNum MagNum::getAbs() {
	MagNum result = *this;
	return result.abs();
}


MagNum MagNum::getSqrt() const {
	MagNum result = *this;
	return result.sqrt();
}

MagNum& MagNum::sqrt() {

	int oldExp = getMpfrExp();
	int oldPrecision = getCurrentPrecision();

	mpfr_sqrt(number, number, RND_MODE);

	evaluateNewPrecision(MAX(oldExp, getMpfrExp()), MAX(oldPrecision, getCurrentPrecision()), ROOT_CORRECTION);
	notifyOver100Listeners();
	return *this;
}

MagNum MagNum::getCbrt() const {
	MagNum result = *this;
	return result.cbrt();
}

MagNum& MagNum::cbrt() {

	int oldExp = getMpfrExp();
	int oldPrecision = getCurrentPrecision();

	mpfr_cbrt(number, number, RND_MODE);

	evaluateNewPrecision(MAX(oldExp, getMpfrExp()), MAX(oldPrecision, getCurrentPrecision()), ROOT_CORRECTION);
	notifyOver100Listeners();
	return *this;
}

MagNum MagNum::getRoot(long arg) const {
	MagNum result = *this;
	return result.root(arg);
}

MagNum& MagNum::root(long arg) {

	int oldExp = getMpfrExp();
	int oldPrecision = getCurrentPrecision();

	mpfr_root(number, number, arg, RND_MODE);

	evaluateNewPrecision(MAX(oldExp, getMpfrExp()), MAX(oldPrecision, getCurrentPrecision()), ROOT_CORRECTION);
	notifyOver100Listeners();
	return *this;
}

MagNum MagNum::getPow(const MagNum& arg) const {
	MagNum result = *this;
	return result.pow(arg);
}

MagNum& MagNum::pow(const MagNum& arg) {

	int oldExp = getMpfrExp();
	int oldPrecision = getCurrentPrecision();

	mpfr_pow(number, number, *arg.getMpfr(), RND_MODE);

	evaluateNewPrecision(MAX(oldExp, getMpfrExp()), MAX(oldPrecision, getCurrentPrecision()), POW_CORRECTION);
	notifyOver100Listeners();
	return *this;
}


MagNum MagNum::sin(const MagNum& arg) {
	MagNum result = arg;
	return result.sinOf();
}

MagNum MagNum::cos(const MagNum& arg) {
	MagNum result = arg;
	return result.cosOf();
}

MagNum MagNum::tan(const MagNum& arg) {
	MagNum result = arg;
	return result.tanOf();
}

MagNum MagNum::cot(const MagNum& arg) {
	MagNum result = arg;
	return result.cotOf();
}

MagNum MagNum::exp(const MagNum& arg) {
	MagNum result = arg;
	return result.expOf();
}


MagNum& MagNum::sinOf() {

	int oldExp = getMpfrExp();
	int oldPrecision = getCurrentPrecision();

	mpfr_sin(number, number, RND_MODE);

	evaluateNewPrecision(MAX(oldExp, getMpfrExp()), MAX(oldPrecision, getCurrentPrecision()), SIN_CORRECTION);
	notifyOver100Listeners();
	return *this;
}

MagNum& MagNum::cosOf() {

	int oldExp = getMpfrExp();
	int oldPrecision = getCurrentPrecision();

	mpfr_cos(number, number, RND_MODE);

	evaluateNewPrecision(MAX(oldExp, getMpfrExp()), MAX(oldPrecision, getCurrentPrecision()), SIN_CORRECTION);
	notifyOver100Listeners();
	return *this;
}

MagNum& MagNum::tanOf() {

	int oldExp = getMpfrExp();
	int oldPrecision = getCurrentPrecision();

	mpfr_tan(number, number, RND_MODE);

	evaluateNewPrecision(MAX(oldExp, getMpfrExp()), MAX(oldPrecision, getCurrentPrecision()), SIN_CORRECTION);
	notifyOver100Listeners();
	return *this;
}

MagNum& MagNum::cotOf() {

	int oldExp = getMpfrExp();
	int oldPrecision = getCurrentPrecision();

	mpfr_tan(number, number, RND_MODE);
	mpfr_pow_si(number, number, -1, RND_MODE);

	evaluateNewPrecision(MAX(oldExp, getMpfrExp()), MAX(oldPrecision, getCurrentPrecision()), SIN_CORRECTION);
	notifyOver100Listeners();
	return *this;
}

MagNum& MagNum::expOf() {

	int oldExp = getMpfrExp();
	int oldPrecision = getCurrentPrecision();

	mpfr_exp(number, number, RND_MODE);

	evaluateNewPrecision(MAX(oldExp, getMpfrExp()), MAX(oldPrecision, getCurrentPrecision()), EXP_CORRECTION);
	notifyOver100Listeners();
	return *this;
}

void MagNum::print() {
	mpfr_printf ("%+.128Rf\n", number);
}

//	sets default precision instead of instance's
//	void MagNum::setNumberPrecision(int prec) {
//		// TODO: check if it doesnt work too slow after doing more operations
//		mpfr_set_default_prec(prec);
//	}
//
//	int MagNum::getNumberPrecision() const {
//		return mpfr_get_default_prec();
//	}
//

	void MagNum::setCurrentPrecision(int precision) {
		currentPrecision = precision;
	}

	int MagNum::getCurrentPrecision() const {
		return currentPrecision;
	}

	int MagNum::getCriticalPrecision() {
		return MagNum::criticalPrecision;
	}
	void MagNum::setCriticalPrecision(int precision) {
		MagNum::criticalPrecision = precision;
	}

#include <iostream>
	void MagNum::evaluateNewPrecision(int oldExp, int oldPrecision, int correction) {
		int dExp = getMpfrExp() - oldExp;
//		std::cout << "\n" << dExp << "\n";
		currentPrecision = oldPrecision + MAX(dExp + correction, 0);
		if(currentPrecision > criticalPrecision) {
			notifyCriticalPositionListeners();
		}
	}

	void MagNum::evaluateNewPrecision(int oldExp1, int oldExp2, int oldPrecision1, int oldPrecision2, int correction) {
		evaluateNewPrecision(MAX(oldExp1, oldExp2), MAX(oldPrecision1, oldPrecision2), correction);
	}

	MagNum MagNum::getPi() {
		if(MagNum::PI == 0) {
			mpfr_t minusOne, pi;
			mpfr_init2(pi, mpfr_get_default_prec());
			mpfr_init2(minusOne, mpfr_get_default_prec());
			mpfr_set_si(minusOne, -1, RND_MODE);
			mpfr_acos(pi, minusOne, RND_MODE);
			mpfr_clear(minusOne);
			MagNum::PI = MagNum(&pi);
		}
		return MagNum::PI;
	}

	MagNum MagNum::PI = MagNum();
	const MagNum MagNum::ZERO = MagNum();
	const MagNum MagNum::ONE = MagNum(1);
	const MagNum MagNum::TWO = MagNum(2);


	void MagNum::addOver100Listener(Over100Listener* listener) {
		over100Listeners.insert(over100Listeners.begin(), listener);
	}

	void MagNum::addCriticalPrecisionListener(CriticalPrecisionListener* listener) {
		criticalPrecisionListeners.insert(criticalPrecisionListeners.begin(), listener);
	}


	void MagNum::notifyOver100Listeners() {

		if(*this > 100) {
			std::list<Over100Listener*>::iterator it;
			for(it = over100Listeners.begin(); it != over100Listeners.end(); ++it) {
				(*it)->onOver100(*this);
			}
		}
	}


	void MagNum::notifyCriticalPositionListeners() {

		std::list<CriticalPrecisionListener*>::iterator it;
		for(it = criticalPrecisionListeners.begin(); it != criticalPrecisionListeners.end(); ++it) {
			(*it)->onCriticalPrecisionReached(*this);
		}
		for(it = globalCriticalPrecisionListeners.begin(); it != globalCriticalPrecisionListeners.end(); ++it) {
			(*it)->onCriticalPrecisionReached(*this);
		}
	}
